package io.kiki.sba.registry.client.provider;

import io.kiki.sba.registry.client.api.ClientConfig;
import io.kiki.sba.registry.client.api.Subscriber;
import io.kiki.sba.registry.client.api.SubscriberDataObserver;
import io.kiki.sba.registry.client.api.model.UserData;
import io.kiki.sba.registry.client.api.registration.SubscriberRegistration;
import io.kiki.sba.registry.client.model.SegmentData;
import io.kiki.sba.registry.client.task.TaskEvent;
import io.kiki.sba.registry.client.task.Worker;
import io.kiki.sba.registry.core.constants.EventTypeConstants;
import io.kiki.sba.registry.core.model.Data;
import io.kiki.sba.registry.core.model.Scope;
import io.kiki.sba.registry.core.model.SubscriberRegister;
import lombok.Getter;
import lombok.Setter;

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

@Setter
@Getter

public class SubscriberImpl extends AbstractInternalRegister implements Subscriber {

    private final String REGIST_ID;
    @Setter
    @Getter
    private SubscriberDataObserver subscriberDataObserver;
    private ConcurrentHashMap<String, SegmentData> data = new ConcurrentHashMap<String, SegmentData>();
    private AtomicBoolean init = new AtomicBoolean(false);
    private ClientConfig clientConfig;
    private SubscriberRegistration subscriberRegistration;
    private Worker worker;
    private volatile String localZone;
    private List<String> availableSegments = new ArrayList<String>();


    SubscriberImpl(SubscriberRegistration subscriberRegistration, Worker worker, ClientConfig clientConfig) {
        if (null != subscriberRegistration) {
            this.subscriberDataObserver = subscriberRegistration.getSubscriberDataObserver();
        }
        this.subscriberRegistration = subscriberRegistration;
        this.worker = worker;
        this.clientConfig = clientConfig;
        this.REGIST_ID = UUID.randomUUID().toString();
        this.localZone = clientConfig.getZone();
        this.getPubVersion().incrementAndGet();
    }


    @Override
    public UserData peekData() {
        readLock.lock();
        try {
            if (!init.get()) {
                // todo sync read from server
                return new DefaultUserData();
            }
            DefaultUserData defaultUserData = new DefaultUserData();
            if (null == localZone) {
                defaultUserData.setLocalZone(clientConfig.getZone());
            } else {
                defaultUserData.setLocalZone(localZone);
            }
            Map<String, List<String>> map = new HashMap<String, List<String>>();
            Set<Entry<String, SegmentData>> entrySet = data.entrySet();
            for (Entry<String, SegmentData> entry : entrySet) {
                String segment = entry.getKey();
                SegmentData segmentData = entry.getValue();
                if (null == segmentData) {
                    continue;
                }
                // only accept available segments, when available segments is empty accept all
                if (availableSegments != null && !availableSegments.contains(segment)) {
                    continue;
                }
                Map<String, List<Data>> data = segmentData.getData();
                for (Entry<String, List<Data>> entry1 : data.entrySet()) {
                    String zone = entry1.getKey();
                    map.putIfAbsent(zone, new ArrayList<>());
                    List<String> list = map.get(zone);
                    List<Data> dataList = entry1.getValue();
                    for (Data dataBox : dataList) {
                        list.add(dataBox.getData());
                    }
                }
            }
            defaultUserData.setZoneToListMap(map);
            return defaultUserData;
        } finally {
            readLock.unlock();
        }
    }


    @Override
    public Scope getScopeEnum() {
        return subscriberRegistration.getScope();
    }


    @Override
    public void unregister() {
        if (isEnabled()) {
            super.unregister();
            worker.schedule(new TaskEvent(this));
        }
    }


    @Override
    public SubscriberRegister assembly() {
        readLock.lock();
        SubscriberRegister subscriberRegister;
        try {
            if (null == subscriberRegistration.getScope()) {
                subscriberRegistration.setScope(Scope.zone);
            }

            subscriberRegister = new SubscriberRegister();
            subscriberRegister.setRegistId(REGIST_ID);
            subscriberRegister.setScope(subscriberRegistration.getScope().name());
            setAttributes(subscriberRegistration, subscriberRegister, clientConfig);
            // auth signature
            setAuthSignature(subscriberRegister);

            if (isEnabled()) {
                subscriberRegister.setEventType(EventTypeConstants.REGISTER);
            } else {
                subscriberRegister.setEventType(EventTypeConstants.UNREGISTER);
            }
        } finally {
            readLock.unlock();
        }
        return subscriberRegister;
    }

    public void putReceivedData(SegmentData segmentData, String localZone) {
        writeLock.lock();
        try {
            putSegmentData(segmentData);
            this.localZone = localZone;
        } finally {
            writeLock.unlock();
        }
    }

    private void putSegmentData(SegmentData segmentData) {
        if (null != segmentData) {

            SegmentData existsData = data.putIfAbsent(segmentData.getSegment(), segmentData);
            if (null == existsData) {
                init.compareAndSet(false, true);
                return;
            }

            if (existsData.getVersion() < segmentData.getVersion()) {
                boolean result = data.replace(segmentData.getSegment(), existsData, segmentData);
                if (!result) {
                    putSegmentData(segmentData);
                }
                init.compareAndSet(false, true);
            }
        }
    }


    @Override
    public String getDataId() {
        return subscriberRegistration.getDataId();
    }


    @Override
    public String getGroup() {
        return subscriberRegistration.getGroup();
    }


    @Override
    public String getRegistId() {
        return REGIST_ID;
    }


    public List<String> getAvailableSegments() {
        readLock.lock();
        try {
            return new ArrayList<String>(availableSegments);
        } finally {
            readLock.unlock();
        }
    }

    public void setAvailableSegments(List<String> availableSegments) {
        writeLock.lock();
        try {
            if (null == availableSegments) {
                this.availableSegments = new ArrayList<>();
            } else {
                this.availableSegments = new ArrayList<>(availableSegments);
            }
        } finally {
            writeLock.unlock();
        }
    }

    public boolean isInited() {
        return init.get();
    }


}
